1   /*
2    * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package javax.management;
27  
28  import java.io.IOException;
29  import java.io.StreamCorruptedException;
30  import java.io.Serializable;
31  import java.io.ObjectOutputStream;
32  import java.io.ObjectInputStream;
33  import java.lang.reflect.Method;
34  import java.util.Arrays;
35  import java.util.Map;
36  import java.util.WeakHashMap;
37  import java.security.AccessController;
38  import java.security.PrivilegedAction;
39  
40  import static javax.management.ImmutableDescriptor.nonNullDescriptor;
41  
42  /**
43   * <p>Describes the management interface exposed by an MBean; that is,
44   * the set of attributes and operations which are available for
45   * management operations.  Instances of this class are immutable.
46   * Subclasses may be mutable but this is not recommended.</p>
47   *
48   * <p id="info-changed">Usually the {@code MBeanInfo} for any given MBean does
49   * not change over the lifetime of that MBean.  Dynamic MBeans can change their
50   * {@code MBeanInfo} and in that case it is recommended that they emit a {@link
51   * Notification} with a {@linkplain Notification#getType() type} of {@code
52   * "jmx.mbean.info.changed"} and a {@linkplain Notification#getUserData()
53   * userData} that is the new {@code MBeanInfo}.  This is not required, but
54   * provides a conventional way for clients of the MBean to discover the change.
55   * See also the <a href="Descriptor.html#immutableInfo">immutableInfo</a> and
56   * <a href="Descriptor.html#infoTimeout">infoTimeout</a> fields in the {@code
57   * MBeanInfo} {@link Descriptor}.</p>
58   *
59   * <p>The contents of the <code>MBeanInfo</code> for a Dynamic MBean
60   * are determined by its {@link DynamicMBean#getMBeanInfo
61   * getMBeanInfo()} method.  This includes Open MBeans and Model
62   * MBeans, which are kinds of Dynamic MBeans.</p>
63   *
64   * <p>The contents of the <code>MBeanInfo</code> for a Standard MBean
65   * are determined by the MBean server as follows:</p>
66   *
67   * <ul>
68   *
69   * <li>{@link #getClassName()} returns the Java class name of the MBean
70   * object;
71   *
72   * <li>{@link #getConstructors()} returns the list of all public
73   * constructors in that object;
74   *
75   * <li>{@link #getAttributes()} returns the list of all attributes
76   * whose existence is deduced from the presence in the MBean interface
77   * of a <code>get<i>Name</i></code>, <code>is<i>Name</i></code>, or
78   * <code>set<i>Name</i></code> method that conforms to the conventions
79   * for Standard MBeans;
80   *
81   * <li>{@link #getOperations()} returns the list of all methods in
82   * the MBean interface that do not represent attributes;
83   *
84   * <li>{@link #getNotifications()} returns an empty array if the MBean
85   * does not implement the {@link NotificationBroadcaster} interface,
86   * otherwise the result of calling {@link
87   * NotificationBroadcaster#getNotificationInfo()} on it;
88   *
89   * <li>{@link #getDescriptor()} returns a descriptor containing the contents
90   * of any descriptor annotations in the MBean interface (see
91   * {@link DescriptorKey &#64;DescriptorKey}).
92   *
93   * </ul>
94   *
95   * <p>The description returned by {@link #getDescription()} and the
96   * descriptions of the contained attributes and operations are not specified.</p>
97   *
98   * <p>The remaining details of the <code>MBeanInfo</code> for a
99   * Standard MBean are not specified.  This includes the description of
100  * any contained constructors, and notifications; the names
101  * of parameters to constructors and operations; and the descriptions of
102  * constructor parameters.</p>
103  *
104  * @since 1.5
105  */
106 public class MBeanInfo implements Cloneable, Serializable, DescriptorRead {
107 
108     /* Serial version */
109     static final long serialVersionUID = -6451021435135161911L;
110 
111     /**
112      * @serial The Descriptor for the MBean.  This field
113      * can be null, which is equivalent to an empty Descriptor.
114      */
115     private transient Descriptor descriptor;
116 
117     /**
118      * @serial The human readable description of the class.
119      */
120     private final String description;
121 
122     /**
123      * @serial The MBean qualified name.
124      */
125     private final String className;
126 
127     /**
128      * @serial The MBean attribute descriptors.
129      */
130     private final MBeanAttributeInfo[] attributes;
131 
132     /**
133      * @serial The MBean operation descriptors.
134      */
135     private final MBeanOperationInfo[] operations;
136 
137      /**
138      * @serial The MBean constructor descriptors.
139      */
140     private final MBeanConstructorInfo[] constructors;
141 
142     /**
143      * @serial The MBean notification descriptors.
144      */
145     private final MBeanNotificationInfo[] notifications;
146 
147     private transient int hashCode;
148 
149     /**
150      * <p>True if this class is known not to override the array-valued
151      * getters of MBeanInfo.  Obviously true for MBeanInfo itself, and true
152      * for a subclass where we succeed in reflecting on the methods
153      * and discover they are not overridden.</p>
154      *
155      * <p>The purpose of this variable is to avoid cloning the arrays
156      * when doing operations like {@link #equals} where we know they
157      * will not be changed.  If a subclass overrides a getter, we
158      * cannot access the corresponding array directly.</p>
159      */
160     private final transient boolean arrayGettersSafe;
161 
162     /**
163      * Constructs an <CODE>MBeanInfo</CODE>.
164      *
165      * @param className The name of the Java class of the MBean described
166      * by this <CODE>MBeanInfo</CODE>.  This value may be any
167      * syntactically legal Java class name.  It does not have to be a
168      * Java class known to the MBean server or to the MBean's
169      * ClassLoader.  If it is a Java class known to the MBean's
170      * ClassLoader, it is recommended but not required that the
171      * class's public methods include those that would appear in a
172      * Standard MBean implementing the attributes and operations in
173      * this MBeanInfo.
174      * @param description A human readable description of the MBean (optional).
175      * @param attributes The list of exposed attributes of the MBean.
176      * This may be null with the same effect as a zero-length array.
177      * @param constructors The list of public constructors of the
178      * MBean.  This may be null with the same effect as a zero-length
179      * array.
180      * @param operations The list of operations of the MBean.  This
181      * may be null with the same effect as a zero-length array.
182      * @param notifications The list of notifications emitted.  This
183      * may be null with the same effect as a zero-length array.
184      */
185     public MBeanInfo(String className,
186                      String description,
187                      MBeanAttributeInfo[] attributes,
188                      MBeanConstructorInfo[] constructors,
189                      MBeanOperationInfo[] operations,
190                      MBeanNotificationInfo[] notifications)
191             throws IllegalArgumentException {
192         this(className, description, attributes, constructors, operations,
193              notifications, null);
194     }
195 
196     /**
197      * Constructs an <CODE>MBeanInfo</CODE>.
198      *
199      * @param className The name of the Java class of the MBean described
200      * by this <CODE>MBeanInfo</CODE>.  This value may be any
201      * syntactically legal Java class name.  It does not have to be a
202      * Java class known to the MBean server or to the MBean's
203      * ClassLoader.  If it is a Java class known to the MBean's
204      * ClassLoader, it is recommended but not required that the
205      * class's public methods include those that would appear in a
206      * Standard MBean implementing the attributes and operations in
207      * this MBeanInfo.
208      * @param description A human readable description of the MBean (optional).
209      * @param attributes The list of exposed attributes of the MBean.
210      * This may be null with the same effect as a zero-length array.
211      * @param constructors The list of public constructors of the
212      * MBean.  This may be null with the same effect as a zero-length
213      * array.
214      * @param operations The list of operations of the MBean.  This
215      * may be null with the same effect as a zero-length array.
216      * @param notifications The list of notifications emitted.  This
217      * may be null with the same effect as a zero-length array.
218      * @param descriptor The descriptor for the MBean.  This may be null
219      * which is equivalent to an empty descriptor.
220      *
221      * @since 1.6
222      */
223     public MBeanInfo(String className,
224                      String description,
225                      MBeanAttributeInfo[] attributes,
226                      MBeanConstructorInfo[] constructors,
227                      MBeanOperationInfo[] operations,
228                      MBeanNotificationInfo[] notifications,
229                      Descriptor descriptor)
230             throws IllegalArgumentException {
231 
232         this.className = className;
233 
234         this.description = description;
235 
236         if (attributes == null)
237             attributes = MBeanAttributeInfo.NO_ATTRIBUTES;
238         this.attributes = attributes;
239 
240         if (operations == null)
241             operations = MBeanOperationInfo.NO_OPERATIONS;
242         this.operations = operations;
243 
244         if (constructors == null)
245             constructors = MBeanConstructorInfo.NO_CONSTRUCTORS;
246         this.constructors = constructors;
247 
248         if (notifications == null)
249             notifications = MBeanNotificationInfo.NO_NOTIFICATIONS;
250         this.notifications = notifications;
251 
252         if (descriptor == null)
253             descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
254         this.descriptor = descriptor;
255 
256         this.arrayGettersSafe =
257                 arrayGettersSafe(this.getClass(), MBeanInfo.class);
258     }
259 
260     /**
261      * <p>Returns a shallow clone of this instance.
262      * The clone is obtained by simply calling <tt>super.clone()</tt>,
263      * thus calling the default native shallow cloning mechanism
264      * implemented by <tt>Object.clone()</tt>.
265      * No deeper cloning of any internal field is made.</p>
266      *
267      * <p>Since this class is immutable, the clone method is chiefly of
268      * interest to subclasses.</p>
269      */
270      @Override
271      public Object clone () {
272          try {
273              return super.clone() ;
274          } catch (CloneNotSupportedException e) {
275              // should not happen as this class is cloneable
276              return null;
277          }
278      }
279 
280 
281     /**
282      * Returns the name of the Java class of the MBean described by
283      * this <CODE>MBeanInfo</CODE>.
284      *
285      * @return the class name.
286      */
287     public String getClassName()  {
288         return className;
289     }
290 
291     /**
292      * Returns a human readable description of the MBean.
293      *
294      * @return the description.
295      */
296     public String getDescription()  {
297         return description;
298     }
299 
300     /**
301      * Returns the list of attributes exposed for management.
302      * Each attribute is described by an <CODE>MBeanAttributeInfo</CODE> object.
303      *
304      * The returned array is a shallow copy of the internal array,
305      * which means that it is a copy of the internal array of
306      * references to the <CODE>MBeanAttributeInfo</CODE> objects
307      * but that each referenced <CODE>MBeanAttributeInfo</CODE> object is not copied.
308      *
309      * @return  An array of <CODE>MBeanAttributeInfo</CODE> objects.
310      */
311     public MBeanAttributeInfo[] getAttributes()   {
312         MBeanAttributeInfo[] as = nonNullAttributes();
313         if (as.length == 0)
314             return as;
315         else
316             return as.clone();
317     }
318 
319     private MBeanAttributeInfo[] fastGetAttributes() {
320         if (arrayGettersSafe)
321             return nonNullAttributes();
322         else
323             return getAttributes();
324     }
325 
326     /**
327      * Return the value of the attributes field, or an empty array if
328      * the field is null.  This can't happen with a
329      * normally-constructed instance of this class, but can if the
330      * instance was deserialized from another implementation that
331      * allows the field to be null.  It would be simpler if we enforced
332      * the class invariant that these fields cannot be null by writing
333      * a readObject() method, but that would require us to define the
334      * various array fields as non-final, which is annoying because
335      * conceptually they are indeed final.
336      */
337     private MBeanAttributeInfo[] nonNullAttributes() {
338         return (attributes == null) ?
339             MBeanAttributeInfo.NO_ATTRIBUTES : attributes;
340     }
341 
342     /**
343      * Returns the list of operations  of the MBean.
344      * Each operation is described by an <CODE>MBeanOperationInfo</CODE> object.
345      *
346      * The returned array is a shallow copy of the internal array,
347      * which means that it is a copy of the internal array of
348      * references to the <CODE>MBeanOperationInfo</CODE> objects
349      * but that each referenced <CODE>MBeanOperationInfo</CODE> object is not copied.
350      *
351      * @return  An array of <CODE>MBeanOperationInfo</CODE> objects.
352      */
353     public MBeanOperationInfo[] getOperations()  {
354         MBeanOperationInfo[] os = nonNullOperations();
355         if (os.length == 0)
356             return os;
357         else
358             return os.clone();
359     }
360 
361     private MBeanOperationInfo[] fastGetOperations() {
362         if (arrayGettersSafe)
363             return nonNullOperations();
364         else
365             return getOperations();
366     }
367 
368     private MBeanOperationInfo[] nonNullOperations() {
369         return (operations == null) ?
370             MBeanOperationInfo.NO_OPERATIONS : operations;
371     }
372 
373     /**
374      * <p>Returns the list of the public constructors of the MBean.
375      * Each constructor is described by an
376      * <CODE>MBeanConstructorInfo</CODE> object.</p>
377      *
378      * <p>The returned array is a shallow copy of the internal array,
379      * which means that it is a copy of the internal array of
380      * references to the <CODE>MBeanConstructorInfo</CODE> objects but
381      * that each referenced <CODE>MBeanConstructorInfo</CODE> object
382      * is not copied.</p>
383      *
384      * <p>The returned list is not necessarily exhaustive.  That is,
385      * the MBean may have a public constructor that is not in the
386      * list.  In this case, the MBean server can construct another
387      * instance of this MBean's class using that constructor, even
388      * though it is not listed here.</p>
389      *
390      * @return  An array of <CODE>MBeanConstructorInfo</CODE> objects.
391      */
392     public MBeanConstructorInfo[] getConstructors()  {
393         MBeanConstructorInfo[] cs = nonNullConstructors();
394         if (cs.length == 0)
395             return cs;
396         else
397             return cs.clone();
398     }
399 
400     private MBeanConstructorInfo[] fastGetConstructors() {
401         if (arrayGettersSafe)
402             return nonNullConstructors();
403         else
404             return getConstructors();
405     }
406 
407     private MBeanConstructorInfo[] nonNullConstructors() {
408         return (constructors == null) ?
409             MBeanConstructorInfo.NO_CONSTRUCTORS : constructors;
410     }
411 
412     /**
413      * Returns the list of the notifications emitted by the MBean.
414      * Each notification is described by an <CODE>MBeanNotificationInfo</CODE> object.
415      *
416      * The returned array is a shallow copy of the internal array,
417      * which means that it is a copy of the internal array of
418      * references to the <CODE>MBeanNotificationInfo</CODE> objects
419      * but that each referenced <CODE>MBeanNotificationInfo</CODE> object is not copied.
420      *
421      * @return  An array of <CODE>MBeanNotificationInfo</CODE> objects.
422      */
423     public MBeanNotificationInfo[] getNotifications()  {
424         MBeanNotificationInfo[] ns = nonNullNotifications();
425         if (ns.length == 0)
426             return ns;
427         else
428             return ns.clone();
429     }
430 
431     private MBeanNotificationInfo[] fastGetNotifications() {
432         if (arrayGettersSafe)
433             return nonNullNotifications();
434         else
435             return getNotifications();
436     }
437 
438     private MBeanNotificationInfo[] nonNullNotifications() {
439         return (notifications == null) ?
440             MBeanNotificationInfo.NO_NOTIFICATIONS : notifications;
441     }
442 
443     /**
444      * Get the descriptor of this MBeanInfo.  Changing the returned value
445      * will have no affect on the original descriptor.
446      *
447      * @return a descriptor that is either immutable or a copy of the original.
448      *
449      * @since 1.6
450      */
451     public Descriptor getDescriptor() {
452         return (Descriptor) nonNullDescriptor(descriptor).clone();
453     }
454 
455     @Override
456     public String toString() {
457         return
458             getClass().getName() + "[" +
459             "description=" + getDescription() + ", " +
460             "attributes=" + Arrays.asList(fastGetAttributes()) + ", " +
461             "constructors=" + Arrays.asList(fastGetConstructors()) + ", " +
462             "operations=" + Arrays.asList(fastGetOperations()) + ", " +
463             "notifications=" + Arrays.asList(fastGetNotifications()) + ", " +
464             "descriptor=" + getDescriptor() +
465             "]";
466     }
467 
468     /**
469      * <p>Compare this MBeanInfo to another.  Two MBeanInfo objects
470      * are equal if and only if they return equal values for {@link
471      * #getClassName()}, for {@link #getDescription()}, and for
472      * {@link #getDescriptor()}, and the
473      * arrays returned by the two objects for {@link
474      * #getAttributes()}, {@link #getOperations()}, {@link
475      * #getConstructors()}, and {@link #getNotifications()} are
476      * pairwise equal.  Here "equal" means {@link
477      * Object#equals(Object)}, not identity.</p>
478      *
479      * <p>If two MBeanInfo objects return the same values in one of
480      * their arrays but in a different order then they are not equal.</p>
481      *
482      * @param o the object to compare to.
483      *
484      * @return true if and only if <code>o</code> is an MBeanInfo that is equal
485      * to this one according to the rules above.
486      */
487     @Override
488     public boolean equals(Object o) {
489         if (o == this)
490             return true;
491         if (!(o instanceof MBeanInfo))
492             return false;
493         MBeanInfo p = (MBeanInfo) o;
494         if (!isEqual(getClassName(),  p.getClassName()) ||
495                 !isEqual(getDescription(), p.getDescription()) ||
496                 !getDescriptor().equals(p.getDescriptor())) {
497             return false;
498         }
499 
500         return
501             (Arrays.equals(p.fastGetAttributes(), fastGetAttributes()) &&
502              Arrays.equals(p.fastGetOperations(), fastGetOperations()) &&
503              Arrays.equals(p.fastGetConstructors(), fastGetConstructors()) &&
504              Arrays.equals(p.fastGetNotifications(), fastGetNotifications()));
505     }
506 
507     @Override
508     public int hashCode() {
509         /* Since computing the hashCode is quite expensive, we cache it.
510            If by some terrible misfortune the computed value is 0, the
511            caching won't work and we will recompute it every time.
512 
513            We don't bother synchronizing, because, at worst, n different
514            threads will compute the same hashCode at the same time.  */
515         if (hashCode != 0)
516             return hashCode;
517 
518         hashCode =
519             getClassName().hashCode() ^
520             getDescriptor().hashCode() ^
521             arrayHashCode(fastGetAttributes()) ^
522             arrayHashCode(fastGetOperations()) ^
523             arrayHashCode(fastGetConstructors()) ^
524             arrayHashCode(fastGetNotifications());
525 
526         return hashCode;
527     }
528 
529     private static int arrayHashCode(Object[] array) {
530         int hash = 0;
531         for (int i = 0; i < array.length; i++)
532             hash ^= array[i].hashCode();
533         return hash;
534     }
535 
536     /**
537      * Cached results of previous calls to arrayGettersSafe.  This is
538      * a WeakHashMap so that we don't prevent a class from being
539      * garbage collected just because we know whether it's immutable.
540      */
541     private static final Map<Class<?>, Boolean> arrayGettersSafeMap =
542         new WeakHashMap<Class<?>, Boolean>();
543 
544     /**
545      * Return true if <code>subclass</code> is known to preserve the
546      * immutability of <code>immutableClass</code>.  The class
547      * <code>immutableClass</code> is a reference class that is known
548      * to be immutable.  The subclass <code>subclass</code> is
549      * considered immutable if it does not override any public method
550      * of <code>immutableClass</code> whose name begins with "get".
551      * This is obviously not an infallible test for immutability,
552      * but it works for the public interfaces of the MBean*Info classes.
553     */
554     static boolean arrayGettersSafe(Class<?> subclass, Class<?> immutableClass) {
555         if (subclass == immutableClass)
556             return true;
557         synchronized (arrayGettersSafeMap) {
558             Boolean safe = arrayGettersSafeMap.get(subclass);
559             if (safe == null) {
560                 try {
561                     ArrayGettersSafeAction action =
562                         new ArrayGettersSafeAction(subclass, immutableClass);
563                     safe = AccessController.doPrivileged(action);
564                 } catch (Exception e) { // e.g. SecurityException
565                     /* We don't know, so we assume it isn't.  */
566                     safe = false;
567                 }
568                 arrayGettersSafeMap.put(subclass, safe);
569             }
570             return safe;
571         }
572     }
573 
574     /*
575      * The PrivilegedAction stuff is probably overkill.  We can be
576      * pretty sure the caller does have the required privileges -- a
577      * JMX user that can't do reflection can't even use Standard
578      * MBeans!  But there's probably a performance gain by not having
579      * to check the whole call stack.
580      */
581     private static class ArrayGettersSafeAction
582             implements PrivilegedAction<Boolean> {
583 
584         private final Class<?> subclass;
585         private final Class<?> immutableClass;
586 
587         ArrayGettersSafeAction(Class<?> subclass, Class<?> immutableClass) {
588             this.subclass = subclass;
589             this.immutableClass = immutableClass;
590         }
591 
592         public Boolean run() {
593             Method[] methods = immutableClass.getMethods();
594             for (int i = 0; i < methods.length; i++) {
595                 Method method = methods[i];
596                 String methodName = method.getName();
597                 if (methodName.startsWith("get") &&
598                         method.getParameterTypes().length == 0 &&
599                         method.getReturnType().isArray()) {
600                     try {
601                         Method submethod =
602                             subclass.getMethod(methodName);
603                         if (!submethod.equals(method))
604                             return false;
605                     } catch (NoSuchMethodException e) {
606                         return false;
607                     }
608                 }
609             }
610             return true;
611         }
612     }
613 
614     private static boolean isEqual(String s1, String s2) {
615         boolean ret;
616 
617         if (s1 == null) {
618             ret = (s2 == null);
619         } else {
620             ret = s1.equals(s2);
621         }
622 
623         return ret;
624     }
625 
626     /**
627      * Serializes an {@link MBeanInfo} to an {@link ObjectOutputStream}.
628      * @serialData
629      * For compatibility reasons, an object of this class is serialized as follows.
630      * <ul>
631      * The method {@link ObjectOutputStream#defaultWriteObject defaultWriteObject()}
632      * is called first to serialize the object except the field {@code descriptor}
633      * which is declared as transient. The field {@code descriptor} is serialized
634      * as follows:
635      *     <ul>
636      *     <li> If {@code descriptor} is an instance of the class
637      *        {@link ImmutableDescriptor}, the method {@link ObjectOutputStream#write
638      *        write(int val)} is called to write a byte with the value {@code 1},
639      *        then the method {@link ObjectOutputStream#writeObject writeObject(Object obj)}
640      *        is called twice to serialize the field names and the field values of the
641      *        {@code descriptor}, respectively as a {@code String[]} and an
642      *        {@code Object[]};</li>
643      *     <li> Otherwise, the method {@link ObjectOutputStream#write write(int val)}
644      *        is called to write a byte with the value {@code 0}, then the method
645      *        {@link ObjectOutputStream#writeObject writeObject(Object obj)} is called
646      *        to serialize the field {@code descriptor} directly.
647      *     </ul>
648      * </ul>
649      * @since 1.6
650      */
651     private void writeObject(ObjectOutputStream out) throws IOException {
652         out.defaultWriteObject();
653 
654         if (descriptor.getClass() == ImmutableDescriptor.class) {
655             out.write(1);
656 
657             final String[] names = descriptor.getFieldNames();
658 
659             out.writeObject(names);
660             out.writeObject(descriptor.getFieldValues(names));
661         } else {
662             out.write(0);
663 
664             out.writeObject(descriptor);
665         }
666     }
667 
668     /**
669      * Deserializes an {@link MBeanInfo} from an {@link ObjectInputStream}.
670      * @serialData
671      * For compatibility reasons, an object of this class is deserialized as follows.
672      * <ul>
673      * The method {@link ObjectInputStream#defaultReadObject defaultReadObject()}
674      * is called first to deserialize the object except the field
675      * {@code descriptor}, which is not serialized in the default way. Then the method
676      * {@link ObjectInputStream#read read()} is called to read a byte, the field
677      * {@code descriptor} is deserialized according to the value of the byte value:
678      *    <ul>
679      *    <li>1. The method {@link ObjectInputStream#readObject readObject()}
680      *       is called twice to obtain the field names (a {@code String[]}) and
681      *       the field values (a {@code Object[]}) of the {@code descriptor}.
682      *       The two obtained values then are used to construct
683      *       an {@link ImmutableDescriptor} instance for the field
684      *       {@code descriptor};</li>
685      *    <li>0. The value for the field {@code descriptor} is obtained directly
686      *       by calling the method {@link ObjectInputStream#readObject readObject()}.
687      *       If the obtained value is null, the field {@code descriptor} is set to
688      *       {@link ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR};</li>
689      *    <li>-1. This means that there is no byte to read and that the object is from
690      *       an earlier version of the JMX API. The field {@code descriptor} is set to
691      *       {@link ImmutableDescriptor#EMPTY_DESCRIPTOR EMPTY_DESCRIPTOR}.</li>
692      *    <li>Any other value. A {@link StreamCorruptedException} is thrown.</li>
693      *    </ul>
694      * </ul>
695      * @since 1.6
696      */
697 
698     private void readObject(ObjectInputStream in)
699         throws IOException, ClassNotFoundException {
700 
701         in.defaultReadObject();
702 
703         switch (in.read()) {
704         case 1:
705             final String[] names = (String[])in.readObject();
706 
707             if (names.length == 0) {
708                 descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
709             } else {
710                 final Object[] values = (Object[])in.readObject();
711                 descriptor = new ImmutableDescriptor(names, values);
712             }
713 
714             break;
715         case 0:
716             descriptor = (Descriptor)in.readObject();
717 
718             if (descriptor == null) {
719                 descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
720             }
721 
722             break;
723         case -1: // from an earlier version of the JMX API
724             descriptor = ImmutableDescriptor.EMPTY_DESCRIPTOR;
725 
726             break;
727         default:
728             throw new StreamCorruptedException("Got unexpected byte.");
729         }
730     }
731 }